package com.ls.mybatis.core;


import org.dom4j.*;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 会话工厂
 */
public class SqlSessionFactory {
    private String config = "mybatis-config.xml"; //默认读取的配置文件
    //存放数据库连接信息
    private Map<String, String> dataSourceMap = new Hashtable<String, String>();
    //存放映射文件路径
    private List<String> mappers = new Vector<String>();
    //以映射文件中namespace.id为键，执行语句信息为值
    private Map<String, MapperInfo> mapperInfos = new Hashtable<String, MapperInfo>();
    //数据库连接池
    private ConnectionPool connectionPool;

    protected SqlSessionFactory(){
        init();
    }

    /**
     * 读取特定的配置文件
     * @param config
     */
    protected SqlSessionFactory(String config){
        this.config = config;
        init();
    }
    /**
     * 1：开始解析实体类映射文件和解析配置文件
     * 2：创建数据库连接池
     */
    private void init(){
        parseConfigXml(); //解析mybatis配置文件

        /*
        输出内容看一下验证是否正确
        dataSourceMap.forEach((key, val) ->{
            System.out.println(key + ":" + val);
        });
        mappers.forEach(System.out::println);
        */
        parseMapperXml(); //解析映射文件

        /*
        mapperInfos.forEach((key, val) ->{
            System.out.println(key + ":" + val);
        });
        */
        //创建数据库连接池
        ConnectionPoolFactory.create(dataSourceMap.get("driver"), dataSourceMap.get("url"),
                dataSourceMap.get("username"), dataSourceMap.get("password"));
        //获取一个数据库连接池
        connectionPool = ConnectionPoolFactory.getInstance();

    }

    /**
     * 解析mybatis配置文件
     */
    private void parseConfigXml() {
        SAXReader reader = new SAXReader();
        try(InputStream is = this.getClass().getClassLoader().getResourceAsStream(config)){
            if( is == null){
                throw new RuntimeException("读取配置文件失败");
            }
            //讲xml文件转换成文案档树
            Document doc = reader.read(is);
            //通过xpath从文档树中查询我们需要的节点
            //XPath xPath = doc.createXPath("//dataSource/property");
            //List<Node> nodeList = xPath.selectNodes(doc);
            List<Node> nodeList = doc.selectNodes("//dataSource/property");
            if( nodeList == null || nodeList.isEmpty()){
                System.out.println("读取数据库配置信息失败");
            }
            /**
             * 由于dom4j dom4j由1.6.1更新到2.1.1后，List<Node>无法自动转换为List<Element>,
             * 因此需要在遍历时将List中的Node元素单独转换为Element
             */
            for(Node node : nodeList){
                Element el = (Element)node;
                //将数据库信息存入map中
                dataSourceMap.put(el.attributeValue("name"), el.attributeValue("value"));
            }

            //读取映射文件
            //xPath = doc.createXPath("//mappers/mapper");
            //List<Node> nodeList1 = xPath.selectNodes(doc);
            List<Node> nodeList1 = doc.selectNodes("//mappers/mapper");
            if( nodeList1 == null || nodeList1.isEmpty()){
                System.out.println("读取mapper映射文件失败");
            }
            /**
             * 由于dom4j dom4j由1.6.1更新到2.1.1后，List<Node>无法自动转换为List<Element>,
             * 因此需要在遍历时将List中的Node元素单独转换为Element
             */
            for(Node node : nodeList1){
                Element el = (Element)node;
                //将映射文件存入list中
                mappers.add(el.attributeValue("resource"));
            }
        }catch (IOException e){
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     *解析映射文件
     */
    private void parseMapperXml(){
        if(mappers == null || mappers.isEmpty()){
            return;
        }

        MapperInfo mapperInfo = null;
        SAXReader reader = null;
        Document doc = null;
        List<Node> nodeslist = null;

        String basepath = ""; //放在命名空间中的值
        String nodeName = null; //节点名字
        String sql = null;

        Pattern pattern = null; //正则表达式
        Matcher matcher = null;
        List<String> params = null;

        //循环获取所有的映射文件
        for(String mapper : mappers){
            try {
                InputStream is = this.getClass().getClassLoader().getResourceAsStream(mapper);
                if (is == null) {
                    throw new RuntimeException("映射文件" + mapper + "找不到");
                }

                reader = new SAXReader();
                //转换成文档树
                doc = reader.read(is);
                //获取根节点namespace的属性
                basepath = doc.getRootElement().attributeValue("namespace");

                if(!StringUtil.checkNull(basepath)){
                    basepath += ".";
                }

                //找这个文件下所有的节点
                nodeslist = doc.selectNodes("//mapper/*");
                if(nodeslist == null || nodeslist.isEmpty()){
                    continue; //说明文档中的mapper下面没节点，执行下一次循环
                }
                for(Node nodes : nodeslist){
                    Element node = (Element)nodes;
                    //将节点名字全部转成小写
                    nodeName = node.getName().toLowerCase();
                    if(!"update".equals(nodeName) && !"insert".equals(nodeName) && !"select".equals(nodeName)&& !"delete".equals(nodeName)){
                        continue; //不是dml语句不用管
                    }

                    mapperInfo = new MapperInfo();
                    if("select".equals(nodeName)){
                        mapperInfo.setUpdate(false); //说明是查询语句不是更新
                    }
                    mapperInfo.setParameterType(node.attributeValue("parameterType"));
                    mapperInfo.setResultType(node.attributeValue("resultType"));
                    sql = node.getTextTrim(); //获取当前节点的文本值

                    //将sql语句中的取值运算符号#{}替换成？
                    pattern = Pattern.compile("[#][{]\\w+}");
                    matcher = pattern.matcher(sql); //根据匹配规则到sql语句中查找
                    params = new Vector<String>();
                    while ( matcher.find()){
                        //将所有的参数名字保存到list集合中
                        params.add(matcher.group().replaceAll("[#{}]*", ""));
                    }
                    //设置参数名字列表
                    mapperInfo.setParams(params);
                    sql = matcher.replaceAll("?");
                    mapperInfo.setSql(sql);
                    //将获取到的 执行语句信息通过键值对 和每一句id绑定起来
                    mapperInfos.put(basepath + node.attributeValue("id"), mapperInfo);
                }
            } catch (DocumentException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 返回一个会话对象
     * @return
     */
    public SqlSession openSession(){
        return new SqlSession(connectionPool, mapperInfos);
    }
}
